﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Diagnostics;
using AWS.SLAM;
using AWS.SLAM.Configuration;
using AWS.SLAM.Events;
using Microsoft.SharePoint;

namespace AWS.SLAM
{
    /// <summary>
    /// The ContentTypeEventManager contains the SharePoint event handlers for slamming SPListItems based
    /// on ContentTypes.  If a ContentType is configured in SLAM's configuration, this is the set of events
    /// that are attached to that ContentType in SharePoint.
    /// </summary>
    public class ContentTypeEventManager : SPItemEventReceiver
    {
        internal static int _runningReceivers = 0;

        /// <summary>
        /// The lock object for synchronizing Creates
        /// </summary>
        internal static readonly object _createLock = new object();

        /// <summary>
        /// The lock object for synchronizing Deletes
        /// </summary>
        internal static readonly object _deleteLock = new object();

        /// <summary>
        /// The lock object for synchronizing Updates
        /// </summary>
        internal static readonly object _updateLock = new object();

        /// <summary>
        /// Event handler executed when an SPListItem is added to a list with this handler
        /// attached.  This method calls SLAM's IICreator.Create based on this item's ContentType.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemAdded(SPItemEventProperties properties)
        {
            lock (_createLock)
            {
                _runningReceivers++;

                try
                {   
                    base.ItemAdded(properties);
                    SPWeb site = properties.OpenWeb();
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite elevatedSite = new SPSite(site.Site.ID))
                        {
                            using (SPWeb elevatedWeb = elevatedSite.AllWebs[site.ID])
                            {
                                SPList elevatedList = elevatedWeb.Lists[properties.ListItem.ParentList.ID];
                                SPListItem elevatedListItem = null;
                                try
                                {
                                    elevatedListItem = elevatedList.GetItemById(properties.ListItem.ID);
                                }
                                catch
                                {
                                    //SharePoint doesn't provide a method for checking whether a list item
                                    //exists in a List.  If it fails to here we probably are dealing with
                                    //a new document library item, so it will get processed when the rest
                                    //of the required fields are filled in and the update is performed.
                                }

                                if (elevatedListItem != null)
                                {
                                    //Set SLAM Item Context, in the future this will play a larger role
                                    SlamItemContext.ListItem = elevatedListItem;
                                    SlamItemContext.ProcessedByContentType = true;

                                    //Get SLAM configuration
                                    IConfigurationManager configuration = ComponentService.GetInstance<IConfigurationManager>();
                                    //Get the Creator for this item's content type
                                    string contentTypeName = elevatedListItem.ContentType.Name;
                                    if (!String.IsNullOrEmpty(properties.ReceiverData))
                                    {
                                        contentTypeName = properties.ReceiverData;
                                    }
                                    SlamItemContext.TypeName = contentTypeName;
                                    IItemCreator creator = ComponentService.GetInstance<IItemCreator>(elevatedSite.ServerRelativeUrl.TrimStart('/'), elevatedWeb.Name, contentTypeName);

                                    //Disable events and call Create
                                    this.DisableEventFiring();
                                    creator.Create(elevatedListItem);
                                    this.EnableEventFiring();
                                }
                            }
                        }
                    });

                }
                catch (Exception ex)
                {
                    //If an Exception is bubbled up to this point, all other logging must have
                    //failed in which case we will log the exception in the EventLog since this is
                    //an asynchronous event handler
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        if (!EventLog.SourceExists("SLAM"))
                        {
                            EventLog.CreateEventSource("SLAM", "Application");
                        }
                        EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
                    });
                }

                if (_runningReceivers == 1)
                {
                    SlamItemContext.ResetContext();
                }

                _runningReceivers--;
            }
        }

        /// <summary>
        /// Event handler executed when an SPListItem is updated in a list with this handler
        /// attached.  This method calls SLAM's IIUpdater.Update based on this item's ContentType.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemUpdated(SPItemEventProperties properties)
        {
            //If this same item is updated several times in quick succession,
            //make sure each SLAM Update finishes before the next one proceeds
            lock (_updateLock)
            {
                _runningReceivers++;

                try
                {
                    base.ItemUpdated(properties);
                    SPWeb site = properties.OpenWeb();
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite elevatedSite = new SPSite(site.Site.ID))
                        {
                            using (SPWeb elevatedWeb = elevatedSite.AllWebs[site.ID])
                            {
                                SPList elevatedList = elevatedWeb.Lists[properties.ListItem.ParentList.ID];
                                SPListItem elevatedListItem = elevatedList.GetItemById(properties.ListItem.ID);

                                //Set SLAM Item Context, in the future this will play a larger role
                                SlamItemContext.ListItem = elevatedListItem;
                                SlamItemContext.ProcessedByContentType = true;

                                //Get the SLAM configuration
                                IConfigurationManager configuration = ComponentService.GetInstance<IConfigurationManager>();
                                //Get the Updater for this item's content type
                                string contentTypeName = elevatedListItem.ContentType.Name;
                                if (!String.IsNullOrEmpty(properties.ReceiverData))
                                {
                                    contentTypeName = properties.ReceiverData;
                                }
                                SlamItemContext.TypeName = contentTypeName;
                                IItemUpdater updater = ComponentService.GetInstance<IItemUpdater>(elevatedSite.ServerRelativeUrl.TrimStart('/'), elevatedWeb.Name, contentTypeName);

                                //Disable events and call Update
                                this.DisableEventFiring();
                                updater.Update(elevatedListItem);
                                this.EnableEventFiring();
                            }
                        }
                    });
                }
                catch (Exception ex)
                {
                    //If an Exception is bubbled up to this point, all other logging must have
                    //failed in which case we will log the exception in the EventLog since this is
                    //an asynchronous event handler
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        if (!EventLog.SourceExists("SLAM"))
                        {
                            EventLog.CreateEventSource("SLAM", "Application");
                        }
                        EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
                    });
                }

                if (_runningReceivers == 1)
                {
                    SlamItemContext.ResetContext();
                }

                _runningReceivers--;
            }
        }

        /// <summary>
        /// Event handler executed when an SPListItem is being deleted from a list with this handler
        /// attached.  This method calls SLAM's IIDeleter.Delete based on this item's ContentType.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemDeleting(SPItemEventProperties properties)
        {
            lock (_deleteLock)
            {
                _runningReceivers++;

                try
                {
                    base.ItemDeleting(properties);
                    SPWeb site = properties.OpenWeb();
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite elevatedSite = new SPSite(site.Site.ID))
                        {
                            using (SPWeb elevatedWeb = elevatedSite.AllWebs[site.ID])
                            {
                                SPList elevatedList = elevatedWeb.Lists[properties.ListItem.ParentList.ID];
                                SPListItem elevatedListItem = elevatedList.GetItemById(properties.ListItem.ID);

                                //Set SLAM Item Context, in the future this will play a larger role
                                SlamItemContext.ListItem = elevatedListItem;
                                SlamItemContext.ProcessedByContentType = true;

                                //Get the SLAM configuration
                                IConfigurationManager configuration = ComponentService.GetInstance<IConfigurationManager>();
                                //Get the Deleter for this item's content type
                                string contentTypeName = elevatedListItem.ContentType.Name;
                                if (!String.IsNullOrEmpty(properties.ReceiverData))
                                {
                                    contentTypeName = properties.ReceiverData;
                                }
                                SlamItemContext.TypeName = contentTypeName;
                                IItemDeleter deleter = ComponentService.GetInstance<IItemDeleter>(elevatedSite.ServerRelativeUrl.TrimStart('/'), elevatedWeb.Name, contentTypeName);

                                //Disable events and call Delete
                                this.DisableEventFiring();
                                //the Deleter's Delete method returns true if the item should actually be deleted
                                properties.Cancel = !deleter.Delete(elevatedListItem);
                                if (properties.Cancel)
                                {
                                    properties.Status = SPEventReceiverStatus.CancelNoError;
                                }
                                this.EnableEventFiring();
                            }
                        }
                    });
                }
                catch (Exception ex)
                {
                    //If an Exception is bubbled up to this point, all other logging must have
                    //failed in which case we will log the exception in the EventLog.  ItemDeleting is
                    //syncrhonous unlike the other event handlers, but SLAM should not interrupt
                    //a user deleting an item if it fails since SharePoint is not dependent on 
                    //SLAM.
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        if (!EventLog.SourceExists("SLAM"))
                        {
                            EventLog.CreateEventSource("SLAM", "Application");
                        }
                        EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
                    });
                }

                if (_runningReceivers == 1)
                {
                    SlamItemContext.ResetContext();
                }

                _runningReceivers--;
            }
        }
    }
}